package com.lt.flashcard.model.entity;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.zkoss.xel.fn.StringFns;
import org.zkoss.zkplus.hibernate.HibernateUtil;

import com.lt.flashcard.model.service.FlashcardService;
import com.lt.flashcard.model.service.Repository;
import com.lt.flashcard.model.service.TagService;

/**
 * @author tam.nguyentrung
 * @version 1.0
 * @created 15-Aug-2012 17:37:10
 */
@Entity
@Table(name = "flashcardset")
public class FlashcardSet implements java.io.Serializable {

    private static final Logger log = Logger.getLogger(FlashcardSet.class);

    private static final long serialVersionUID = 1L;

    /**
     * Privacy setting for this set
     */
    private int privacy = PRIVACY_PUBLIC;
    public static final int PRIVACY_PUBLIC = 0;
    public static final int PRIVACY_PRIVATE = 1;
    public static final int PRIVACY_FRIEND = 2;

    public static final int SET_NOTYET = 0;
    public static final int SET_APPROVED = 1;
    public static final int LIMIT_COMMENTS = 20;

    private Long id;

    /**
     * Ten cua list flashcard
     */
    private String name = "";
    /**
     * Mo ta ve flashcard
     */
    private String description = "";
    /**
     * Hinh anh dai dien cho flashcard
     */
    private String image = "";
    /**
     * trang thai cua flashcard list
     */
    private int status = STATUS_INVALID;
    public static final int STATUS_INVALID = 0;
    public static final int STATUS_VALID = 1;
    public static final int STATUS_BADCONTENT = 2;
    /**
     * created date flashcard list
     */
    private Date created = new Date();
    /**
     * word thuoc ngon ngu nao
     */
    private String language = "";
    /**
     * Total view count
     */
    private int viewCount = 0;
    /**
     * Store the result of all votes
     */
    private double voteResult = 0;
    /**
     * Total vote count
     */
    private int voteTotal = 0;
    /**
     * Store list id of flashcardset that this relates to
     */
    private String relateflashcardsetCache = "";
    /**
     * Store list id of original flashcardset
     */
    private String originalflashcardsetCache = "";
    /**
     * Privacy custom, store the id of allowed users
     */
    private String allowedusersCache = "";
    /**
     * Privacy custom, store the id of denied users
     */
    private String deniedusersCache = "";
    /**
     * Total number of flashcards that have learned
     */
    private int learnLearned = 0;
    private int learnUnknow = 0;
    /**
     * Total number of flashcard in this set
     */
    private int totalflashcards = 0;
    /**
     * Cache last 5 testing results of this flashcardset
     */
    private String testingresultCache = "";
    private List<Flashcard> flashcards = new ArrayList<Flashcard>();
    /**
     * The author of this flashcardset
     */
    private User author;
    /**
     * The tag list of this flashcardset
     */
    private Set<Tag> tags = new HashSet<Tag>(0);
    private Set<Learnhistory> learnhistories = new HashSet<Learnhistory>(0);

    private int draft = SET_NOTYET;

    private Productcategory productcategory = null;
    private int priceEflashcard = 0;
    private int pricePaper = 0;
    private byte haspaper = 0;
    private byte readysale = 0;
    /**
     * Mark if this is flashcardset of admin
     */
    private int admin = ADMIN_NO;
    public static final int ADMIN_YES = 1;
    public static final int ADMIN_NO = 0;
    public static final int ADMIN_BUY = 2; // This flashcardset bought from blueup

    private int paymenttype = Orderdetail.TYPE_OTHER;
    // Indicate if this is a blank flashcardset
    private int blankflashcardset = 0;
    /**
     * Store detail
     */
    private String storedetail = "";

    private int weight = 0;

    public FlashcardSet() {
        created = new Date();
    }

    public FlashcardSet(FlashcardSet flashcardset) {
        this.name = flashcardset.name;
        this.description = flashcardset.description;
        this.image = flashcardset.image;
        this.status = flashcardset.status;
        this.created = new Date();
        this.language = flashcardset.language;
        this.privacy = PRIVACY_PUBLIC;
        this.viewCount = 0;
        this.voteResult = 0;
        this.voteTotal = 0;
    }

    public FlashcardSet(String name, String description, String image, int status, Date created, String language, int privacy, int viewCount,
            double voteResult, int voteTotal) {
        this.name = name;
        this.description = description;
        this.image = image;
        this.status = status;
        this.created = created;
        this.language = language;
        this.privacy = privacy;
        this.viewCount = viewCount;
        this.voteResult = voteResult;
        this.voteTotal = voteTotal;
    }

    /**
     * Clone a flashcardset to new one
     */
    @Override
    public FlashcardSet clone() {
        FlashcardSet fcs = new FlashcardSet();
        fcs.setPrivacy(PRIVACY_PUBLIC);
        fcs.setName(getName());
        fcs.setDescription(getDescription());
        fcs.setImage(getImage());
        fcs.setStatus(STATUS_VALID);
        fcs.setLanguage(getLanguage());
        fcs.setOriginalflashcardsetCache(String.valueOf(getId()));
        fcs.setTotalflashcards(getFlashcards().size());
        for (Flashcard fc : getFlashcards()) {
            Flashcard nfc = fc.clone();
            fcs.addFlashcard(nfc);
        }
        for (Tag tag : getTags()) {
            fcs.getTags().add(tag);
        }
        fcs.setDraft(SET_APPROVED);
        return fcs;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "name", nullable = false)
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Column(name = "description", nullable = false)
    public String getDescription() {
        return this.description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Column(name = "image", nullable = false)
    public String getImage() {
        return this.image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    @Column(name = "status", nullable = false)
    public int getStatus() {
        return this.status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created", nullable = false, length = 0)
    public Date getCreated() {
        return this.created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    @Column(name = "language", nullable = false, length = 20)
    public String getLanguage() {
        return this.language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    @Column(name = "privacy", nullable = false)
    public int getPrivacy() {
        return this.privacy;
    }

    public void setPrivacy(int privacy) {
        this.privacy = privacy;
    }

    @Column(name = "viewCount", nullable = false)
    public int getViewCount() {
        return this.viewCount;
    }

    public void setViewCount(int viewCount) {
        this.viewCount = viewCount;
    }

    @Column(name = "voteResult", nullable = false, precision = 10)
    public double getVoteResult() {
        return this.voteResult;
    }

    public void setVoteResult(double voteResult) {
        this.voteResult = voteResult;
    }

    @Column(name = "voteTotal", nullable = false)
    public int getVoteTotal() {
        return this.voteTotal;
    }

    public void setVoteTotal(int voteTotal) {
        this.voteTotal = voteTotal;
    }

    @Column(name = "relateflashcardset_cache", nullable = false)
    public String getRelateflashcardsetCache() {
        return this.relateflashcardsetCache;
    }

    public void setRelateflashcardsetCache(String relateflashcardsetCache) {
        this.relateflashcardsetCache = relateflashcardsetCache;
    }

    @Column(name = "originalflashcardset_cache", nullable = false)
    public String getOriginalflashcardsetCache() {
        return this.originalflashcardsetCache;
    }

    public void setOriginalflashcardsetCache(String originalflashcardsetCache) {
        this.originalflashcardsetCache = originalflashcardsetCache;
    }

    @Column(name = "allowedusers_cache", nullable = false)
    public String getAllowedusersCache() {
        return this.allowedusersCache;
    }

    public void setAllowedusersCache(String allowedusersCache) {
        this.allowedusersCache = allowedusersCache;
    }

    @Column(name = "deniedusers_cache", nullable = false)
    public String getDeniedusersCache() {
        return this.deniedusersCache;
    }

    public void setDeniedusersCache(String deniedusersCache) {
        this.deniedusersCache = deniedusersCache;
    }

    @Column(name = "learn_learned", nullable = false)
    public int getLearnLearned() {
        return this.learnLearned;
    }

    public void setLearnLearned(int learnLearned) {
        this.learnLearned = learnLearned;
    }

    @Column(name = "totalflashcards", nullable = false)
    public int getTotalflashcards() {
        return this.totalflashcards;
    }

    public void setTotalflashcards(int totalflashcards) {
        this.totalflashcards = totalflashcards;
    }

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "flashcardSet", cascade = CascadeType.ALL)
    @OrderBy("weight")
    public List<Flashcard> getFlashcards() {
        return flashcards;
    }

    public void setFlashcards(List<Flashcard> flashcards) {
        this.flashcards = flashcards;
    }

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    public User getAuthor() {
        return author;
    }

    public void setAuthor(User author) {
        this.author = author;
    }

    @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST })
    @JoinTable(name = "flashcardset_has_tag", joinColumns = { @JoinColumn(name = "flashcardSet_id", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "tag_id", nullable = false, updatable = false) })
    public
            Set<Tag> getTags() {
        return tags;
    }

    public void setTags(Set<Tag> tags) {
        this.tags = tags;
    }

    /**
     * Check if this set has sound
     * @return
     */
    @Transient
    public boolean hasSound() {
        for (Flashcard fc : getFlashcards()) {
            if (fc == null) {
                continue;
            }
            if (StringUtils.isNotBlank(fc.getSoundBack()) || StringUtils.isNotBlank(fc.getSoundFront())) {
                return true;
            }
        }
        return false;
    }

    @Column(name = "learn_unknow", nullable = false)
    public int getLearnUnknow() {
        return this.learnUnknow;
    }

    public void setLearnUnknow(int learnUnknow) {
        this.learnUnknow = learnUnknow;
    }

    @Transient
    public boolean hasImage() {
        for (Flashcard fc : getFlashcards()) {
            if (fc != null && StringUtils.isNotBlank(fc.getImage())) {
                return true;
            }
        }
        return false;
    }

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "flashcardset", cascade = CascadeType.REMOVE)
    public Set<Learnhistory> getLearnhistories() {
        return this.learnhistories;
    }

    public void setLearnhistories(Set<Learnhistory> learnhistories) {
        this.learnhistories = learnhistories;
    }

    @Column(name = "testingresult_cache", nullable = false)
    public String getTestingresultCache() {
        return this.testingresultCache;
    }

    public void setTestingresultCache(String testingresultCache) {
        this.testingresultCache = testingresultCache;
    }

    @Column(name = "draft", nullable = false)
    public int getDraft() {
        return this.draft;
    }

    public void setDraft(int draft) {
        this.draft = draft;
    }

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ProductCategory_id", nullable = true)
    public Productcategory getProductcategory() {
        return this.productcategory;
    }

    public void setProductcategory(Productcategory productcategory) {
        this.productcategory = productcategory;
    }

    @Column(name = "price_eflashcard", nullable = false)
    public int getPriceEflashcard() {
        return this.priceEflashcard;
    }

    public void setPriceEflashcard(int priceEflashcard) {
        this.priceEflashcard = priceEflashcard;
    }

    @Column(name = "price_paper", nullable = false)
    public int getPricePaper() {
        return this.pricePaper;
    }

    public void setPricePaper(int pricePaper) {
        this.pricePaper = pricePaper;
    }

    @Column(name = "haspaper", nullable = false)
    public byte getHaspaper() {
        return this.haspaper;
    }

    public void setHaspaper(byte haspaper) {
        this.haspaper = haspaper;
    }

    @Column(name = "readysale", nullable = false)
    public byte getReadysale() {
        return this.readysale;
    }

    public void setReadysale(byte readysale) {
        this.readysale = readysale;
    }

    @Column(name = "admin", nullable = false)
    public int getAdmin() {
        return this.admin;
    }

    @Transient
    public String getAdminStr() {
        return admin == 0 ? "Normal" : (admin == 1 ? "Blueup" : "Bought");
    }

    public void setAdmin(int admin) {
        this.admin = admin;
    }

    @Column(name = "paymenttype", nullable = false)
    public int getPaymenttype() {
        return this.paymenttype;
    }

    public void setPaymenttype(int paymenttype) {
        this.paymenttype = paymenttype;
    }

    @Column(name = "storedetail", nullable = false, length = 65535)
    public String getStoredetail() {
        return this.storedetail;
    }

    public void setStoredetail(String storedetail) {
        this.storedetail = storedetail;
    }

    @Column(name = "weight", nullable = false)
    public int getWeight() {
        return this.weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    @Column(name = "blankflashcardset", nullable = false)
    public int getBlankflashcardset() {
        return this.blankflashcardset;
    }

    public void setBlankflashcardset(int blankflashcardset) {
        this.blankflashcardset = blankflashcardset;
    }

    /**
     * Find a flashcard by id
     * @param id
     * @return
     */
    @Transient
    public Flashcard getFlashcardById(Long id) {
        for (Flashcard fc : flashcards) {
            if (fc.getId().equals(id)) {
                return fc;
            }
        }
        return null;
    }

    /**
     * Find a flashcard by term
     * @param term
     * @return
     */
    public Flashcard getFlashcardByTerm(String term) {
        for (Flashcard fc : flashcards) {
            if (fc.getTerm().toLowerCase().equals(term.toLowerCase())) {
                return fc;
            }
        }
        return null;
    }

    /**
     * Check to see if a term exists
     * @param term
     * @return
     */
    @Transient
    public boolean isTermExist(String term) {
        for (Flashcard fc : flashcards) {
            if (fc.getTerm().equals(term)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check to see if a term exists and different from target
     * @param term
     * @param differentFrom
     * @return
     */
    public boolean isTermExist(String term, Long differentFrom) {
        if (differentFrom == null) {
            return isTermExist(term);
        }

        for (Flashcard fc : flashcards) {
            if (fc.getTerm().equals(term) && fc.getId().equals(differentFrom)) {
                return true;
            }
        }
        return false;
    }

    @Transient
    public void updateOriginalCache() {
        // update original cache
        Set<String> cache = new HashSet<String>();
        for (Flashcard fc : flashcards) {
            if (fc.getOriflashcardset() != null) {
                cache.add(String.valueOf(fc.getOriflashcardset().getId()));
            }
        }
        setOriginalflashcardsetCache(StringFns.join(cache.toArray(), ","));
    }

    /**
     * 
     * @param flashcard
     */
    @Transient
    public boolean addFlashcard(Flashcard flashcard) {
        if (flashcard == null) {
            return false;
        }
        if (isTermExist(flashcard.getTerm(), flashcard.getId())) {
            return false;
        }
        getFlashcards().add(flashcard);
        flashcard.setFlashcardSet(this);
        setTotalflashcards(flashcards.size());
        if (flashcard.getOriflashcardset() != null) {
            updateOriginalCache();
        }
        return true;
    }

    /**
     * Remove flashcard from flashcardset
     * @param flashcard
     */
    @Transient
    public void removeFlashcard(Flashcard flashcard) {
        Repository repo = Repository.getInstance();
        // repo.deleteBinaryData(flashcard.getImage());
        // TODO remove audio file
        flashcards.remove(flashcard);
        setTotalflashcards(flashcards.size());
        updateOriginalCache();
    }

    /**
     * Remove flashcard from flashcardset by id
     * @param id
     */
    @Transient
    public void removeFlashcard(Long id) {
        if (id == null) {
            return;
        }
        for (Flashcard fc : flashcards) {
            if (id.equals(fc.getId())) {
                removeFlashcard(fc);
                return;
            }
        }
    }

    @Transient
    public void removeAllFlashcards() {
        if (flashcards.size() > 0) {
            Repository repo = Repository.getInstance();
            FlashcardService srv = repo.getFlashcardService();
            Session ses = HibernateUtil.currentSession();
            for (Flashcard fc : flashcards) {
                // repo.deleteBinaryData(fc.getImage());
                ses.delete(fc);
            }
            flashcards.clear();
            flashcards = new ArrayList<Flashcard>();

            setTotalflashcards(0);
            setOriginalflashcardsetCache("");

            // TODO update original cache of others
            // TODO update other number (num of learned...)
        }
    }

    /**
     * Add a tag by tag name
     * @param tagname
     */
    @Transient
    public void addTags(List<String> tagname) {
        List<String> valid = new ArrayList<String>();
        for (String name : tagname) {
            if (name.length() > 4) {
                valid.add(name);
            }
        }
        if (valid.size() > 0) {
            TagService srv = Repository.getInstance().getTagService();
            List<Tag> taglist = srv.getTagByName(valid);
            tags.addAll(taglist);

            for (Tag t : taglist) {
                valid.remove(t.getName());
            }

            for (String t : valid) {
                try {
                    Tag nt = new Tag();
                    nt.setName(t);
                    srv.save(nt);
                    tags.add(nt);
                } catch (Exception e) {
                    log.error("Can't save new tag", e);
                }
            }
        }
    }

    /**
     * Add a tag
     * @param tag
     */
    @Transient
    public void addTag(Tag tag) {
        tags.add(tag);
    }

    @Transient
    public void removeTag(Tag tag) {
        tags.remove(tag);
    }

    @Transient
    public void removeTag(String tagname) {
        Iterator<Tag> it = tags.iterator();
        while (it.hasNext()) {
            if (it.next().getName().equals(tagname)) {
                it.remove();
                return;
            }
        }
    }
}